/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"


/* prototypes */
SYMTBL_ENT_P _symbol_tbl_new_entry(char *symname, char *file,
				   SYMBOL_TYPE type);



//return NULl on failure
SYMTBL_P symbol_tbl_create(void)
{
    return hashtbl_create(SYMBOL_TBL_HASHTBL_SIZE, NULL);
}

bool symbol_tbl_new_entry_function(SYMTBL_P symbol_tbl, char *symname,
				   char *file, LLIST_P asl)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_FUNCTION);
    entry->u.function.asl = asl;
    entry->u.function.builtin_funcp = NULL;

    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;
}

// void *(*builtin_funcp)(EXPR_P expr)
bool symbol_tbl_new_entry_function_builtin(SYMTBL_P symbol_tbl,
					   char *symname,
					   BUILTIN_FUNC_P builtin_funcp)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, "<built-in function>",
			      SYM_TYPE_FUNCTION);
    entry->u.function.asl = NULL;	//not used
    entry->u.function.builtin_funcp = builtin_funcp;

    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;
}


bool symbol_tbl_new_entry_variable_uninitialized(SYMTBL_P symbol_tbl,
						 char *symname, char *file)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_UNINITIALIZED;
    //val already zeroed out

    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;

}

bool symbol_tbl_new_entry_variable_int(SYMTBL_P symbol_tbl, char *symname,
				       char *file, int val)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_INT;
    entry->u.variable.rvalue.int_val = val;
    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;

}

bool symbol_tbl_new_entry_variable_float(SYMTBL_P symbol_tbl,
					 char *symname, char *file,
					 float val)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_FLOAT;
    entry->u.variable.rvalue.float_val = val;
    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;

}

bool symbol_tbl_new_entry_variable_string(SYMTBL_P symbol_tbl,
					  char *symname, char *file,
					  char *val)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_STRING;
    entry->u.variable.rvalue.string_val = val;
    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;
}

bool symbol_tbl_new_entry_variable_bool(SYMTBL_P symbol_tbl,
					char *symname, char *file,
					bool val)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_BOOL;
    entry->u.variable.rvalue.bool_val = val;
    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;
}

bool symbol_tbl_new_entry_variable_function(SYMTBL_P symbol_tbl,
					    char *symname, char *file,
					    char *func_symname)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_FUNCTION;
    entry->u.variable.rvalue.function_val = func_symname;
    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;
}

bool symbol_tbl_new_entry_variable_list(SYMTBL_P symbol_tbl,
					char *symname, char *file,
					LLIST_P val)
{
    SYMTBL_ENT_P entry =
	_symbol_tbl_new_entry(symname, file, SYM_TYPE_VARIABLE);
    entry->u.variable.type = VAR_TYPE_LIST;
    entry->u.variable.rvalue.list_val = val;
    if (hashtbl_insert(symbol_tbl, symname, entry) == -1)
	return false;
    return true;
}

SYMTBL_ENT_P symbol_tbl_get_entry(SYMTBL_P symbol_tbl, char *symname)
{
    return hashtbl_get(symbol_tbl, symname);
}

//get from local stack top
SYMTBL_ENT_P symbol_tbl_get_entry_variable(char *symname)
{
    SYMTBL_P symbol_tbl;
    SYMTBL_ENT_P entry;


    symbol_tbl = symbol_tbl_local_stack_get_top();
    if (symbol_tbl == NULL)
      {
	  error_println("Reference to empty local symbol tbl");
	  return NULL;
      }

    entry = symbol_tbl_get_entry(symbol_tbl, symname);
    if (entry == NULL)
      {
	  error_println
	      ("Reference to undeclared (local variable) symbol: %s",
	       symname);
	  return NULL;
      }

    if (entry->type != SYM_TYPE_VARIABLE)
      {
	  error_println("Symbol %s is not a variable symbol.", symname);
	  return NULL;
      }
    return entry;
}

//variables always on local symbol tbl(topoflocal stack)
//fatal if not found
SYMTBL_ENT_P symbol_tbl_get_entry_variable_fatal(char *symname)
{
    SYMTBL_ENT_P entry;

    entry = symbol_tbl_get_entry_variable(symname);
    if (entry == NULL)
	fatal_error();		//noreturn

    return entry;
}

void symbol_tbl_print_all_functions(SYMTBL_P symbol_tbl, FILE * fp)
{
    int n;
    struct hashnode_s *node;

    for (n = 0; n < symbol_tbl->size; n++)
      {
	  for (node = symbol_tbl->nodes[n]; node; node = node->next)
	    {
		char *symname = node->key;
		SYMTBL_ENT_P entry = node->data;

		if (entry->type == SYM_TYPE_FUNCTION)
		  {
		      LLIST_P asl = entry->u.function.asl;
		      fprintf(fp, "ASL[%s] %s\n", symname,
			      entry->filename !=
			      NULL ? entry->filename : "(null!)");
		      asl_print(fp, asl);
		  }
	    }
      }
}

bool symbol_tbl_new_entry_variable_from_leafexpr(SYMTBL_P symbol_tbl,
						 char *symname, char *file,
						 EXPR_P leaf_expr)
{
    bool ret;
    if (leaf_expr->type != EXPR_LEAF)
      {
	  error_println("Expected EXPR_LEAF");	//shouldn't happen
	  return false;
      }
    switch (leaf_expr->u.leaf.type)
      {
      case LEAF_INT:
	  ret = symbol_tbl_new_entry_variable_int(symbol_tbl, symname,
						  file,
						  leaf_expr->u.leaf.val.
						  int_val);
	  //TODO: error check these
	  break;
      case LEAF_FLOAT:
	  ret = symbol_tbl_new_entry_variable_float(symbol_tbl, symname,
						    file,
						    leaf_expr->u.leaf.
						    val.float_val);
	  break;
      case LEAF_STRING:
	  ret = symbol_tbl_new_entry_variable_string(symbol_tbl, symname,
						     file,
						     leaf_expr->u.leaf.
						     val.string_val);
	  break;
      case LEAF_BOOL:
	  ret = symbol_tbl_new_entry_variable_bool(symbol_tbl, symname,
						   file,
						   leaf_expr->u.leaf.
						   val.bool_val);
	  break;
      case LEAF_ID:
	  {
	      // leaf_id expr 
	      char *func_symname = leaf_expr->u.leaf.val.id_val;
	      //assert func_symname bound to function (global symtbl), OR bound to a variable of type FUNCTION
	      //TODO: 

	      ret =
		  symbol_tbl_new_entry_variable_function(symbol_tbl,
							 symname, file,
							 func_symname);
	  }
	  break;
      case LEAF_LIST:
	  ret = symbol_tbl_new_entry_variable_list(symbol_tbl, symname,
						   file,
						   leaf_expr->u.leaf.
						   val.list_val);
	  break;

      default:
	  error_println("Unexpected leaf type");	//shouldn't ever get here
	  return false;
      }

    return true;
}

void symbol_tbl_local_stack_push(SYMTBL_P symbol_tbl)
{
    llist_append(symbol_tbl, &glob.symbol_tbl_local_stack);	//append to llist <==> push to stack
}

// return NULL on empty stack.
// frees stack node, butnot actual hashtbl
SYMTBL_P symbol_tbl_local_stack_pop(void)
{
    LLIST_P walker;
    SYMTBL_P ret;

    walker = glob.symbol_tbl_local_stack;

    if (walker == NULL)		//empty stack
	return NULL;

    if (walker->next == NULL)	//only one on stack
      {
	  ret = walker->element;
	  free(walker);
	  glob.symbol_tbl_local_stack = NULL;
	  return ret;
      }

    while (walker->next->next != NULL)
	walker = walker->next;

    /* walker is now at [stack top - 1]  when walker->next->next is NULL */
    ret = walker->next->element;	//get ptr to symboltbl;
    free(walker->next);
    walker->next = NULL;
    return ret;
}

SYMTBL_P symbol_tbl_local_stack_get_top(void)
{
    LLIST_P walker;
    SYMTBL_P ret;

    if (glob.symbol_tbl_local_stack == NULL)	//empty stack
	return NULL;

    walker = glob.symbol_tbl_local_stack;
    while (walker->next != NULL)
	walker = walker->next;

    /* walker is now at stack top  => walker->next is NULL */
    ret = walker->element;	//get ptr to symboltbl;
    return ret;

}

//symname key only used for printing
SYMTBL_ENT_P _symbol_tbl_new_entry(char *symname, char *file,
				   SYMBOL_TYPE type)
{
    SYMTBL_ENT_P entry = checked_calloc(1, sizeof(struct st_symbol_tbl_entry));	//zeroes it out
    entry->filename = file;
    entry->type = type;

    debug2_printf("Adding symbol: %s\n", symname);
    return entry;
}
